package clasificaciondepizzas.operations.canny;

import java.awt.*;
import java.util.*;
import java.lang.Math.*;
import java.util.Stack.*;

/**
 *Canny is an algorithm to apply the canny edge detector to an image
 *@author:Timothy Sharman
 *@see code.iface.canny
 */
public class Canny extends Thread {

    //the Gaussian smoothing operator used as part of the process.
    GaussianSmooth gaussiansmooth;
    //The Contrast stretching operator used as part of the process
//  ContrastStretch contraststretch = new ContrastStretch();
    //the width and height of the output image
    private int d_w;
    private int d_h;
    private int[] dest_1d;

    /**
     *Applies the canny edge detector to the input image
     *@param src_1d The source image as a pixel array
     *@param width width of the destination image in pixels
     *@param height height of the destination image in pixels
     *@param size The size of the kernel used in the smoothing
     *@param theta The gaussian smoothing standard deviation
     *@param lowthresh The low threshold for the tracking
     *@param highthresh The high threshold for the tracking
     *@param scale The amount of scaling to be applied to the image
     *@param offset The amount to be added to each result pixel
     *@return A pixel array containing edges in the image
     */
    //Tim's Canny Edge Detection Algorithm
    //Based on algorithm in Machine Vision (pg 169)
  /*a) assume the image is grey level (hence RR=GG=BB)
    b) use value &0x000000ff to get the BB value
    c) gaussian smooth image
    d) work out gradient magnitude
    e) apply nonmaxima suppression
    f) threshold and detect edges
     */
    public int[] apply_canny(byte[] src_1d, int width, int height, int size,
            float theta, int lowthresh, int highthresh,
            float scale, int offset) {

        d_w = width;
        d_h = height;

        //Setup local variables
        dest_1d = new int[d_w * d_h];
        int[] tmp_1d = new int[d_w * d_h];
        int[][] tmp_2d = new int[d_w][d_h];
        int[][] p_2d = new int[d_w][d_h];
        int[][] q_2d = new int[d_w][d_h];
        int[][] m_2d = new int[d_w][d_h];
        double[][] theta_2d = new double[d_w][d_h];
        int[][] nms = new int[d_w][d_h];
        int[][] delta = new int[d_w][d_h];
        int[][] tracked = new int[d_w][d_h];
        int result;
        int tmp = 0;
        int[] tmp2_1d;


        //Set up the output array
        for (int i = 0; i < dest_1d.length; i++) {
            dest_1d[i] = 0xff000000;
        }

        //Smooth the initial image
        tmp_1d = gaussiansmooth.smooth_image(src_1d, width, height, size, theta);

        //Mask off so that we work with values between 0 and 255
        for (int i = 0; i < tmp_1d.length; i++) {
            tmp_1d[i] = tmp_1d[i] & 0x000000ff;
        }

        //Convert 1_d to 2_d for ease of processing in next stages
        for (int i = 0; i < d_w; i++) {
            for (int j = 0; j < d_h; j++) {
                tmp_2d[i][j] = tmp_1d[i + (j * d_w)];
            }
        }

        //Apply the gradient detection
        for (int i = 0; i < (d_w - 1); i++) {
            for (int j = 0; j < (d_h - 1); j++) {
                p_2d[i][j] = (tmp_2d[i][j + 1] - tmp_2d[i][j]
                        + tmp_2d[i + 1][j + 1] - tmp_2d[i + 1][j]) / 2;
                q_2d[i][j] = (tmp_2d[i][j] - tmp_2d[i + 1][j]
                        + tmp_2d[i][j + 1] - tmp_2d[i + 1][j + 1]) / 2;
                m_2d[i][j] = (int) Math.sqrt(Math.pow((double) p_2d[i][j], 2)
                        + Math.pow((double) q_2d[i][j], 2));
                theta_2d[i][j] = Math.atan2((double) (q_2d[i][j]), (double) (p_2d[i][j]));
            }
        }

        //Resize image
        d_w--;
        d_h--;

        //Apply the nonmaxima suppression

        //First calculate which sector each line appears in

        for (int i = 0; i < d_w; i++) {
            for (int j = 0; j < d_h; j++) {
                delta[i][j] = sector(theta_2d[i][j]);
            }
        }


        //Then apply non maximal suppression
        for (int i = 0; i < (d_w - 1); i++) {
            nms[i][0] = 0;
            nms[i][d_h - 1] = 0;
        }
        for (int j = 0; j < (d_h - 1); j++) {
            nms[0][j] = 0;
            nms[d_w - 1][j] = 0;
        }
        for (int i = 1; i < (d_w - 1); i++) {
            for (int j = 1; j < (d_h - 1); j++) {
                nms[i][j] = suppress(m_2d, delta[i][j], i, j, lowthresh);
            }
        }

        //Resize again!
        d_w = d_w - 2;
        d_h = d_h - 2;

        //Track the image
        tracked = apply_track(nms, d_w, d_h, lowthresh, highthresh);

        //Calculate the output array
        for (int i = 0; i < d_w; i++) {
            for (int j = 0; j < d_h; j++) {
                result = tracked[i][j];
                result = (int) (result * scale);
                result = result + offset;
                if (result > 255) {
                    result = 255;
                }
                if (result < 0) {
                    result = 0;
                }
                dest_1d[(i + (j * (d_w + 3)))] = 0xff000000
                        | result << 16 | result << 8 | result;
            }
        }

        //Change the sizes back
        d_w = d_w + 3;
        d_h = d_h + 3;

        return dest_1d;

    }

    //Function to check which sector the line is in (see Machine Vision pg 171)
    private int sector(double theta) {

        //Converting into degrees from radians, and moving to lie between 0 and 360
        theta = Math.toDegrees(theta);
        theta = theta + 270;
        theta = theta % 360;


        if ((theta >= 337.5) || (theta < 22.5) || ((theta >= 157.5) && (theta < 202.5))) {
            return 0;
        }
        if (((theta >= 22.5) && (theta < 67.5)) || ((theta >= 202.5) && (theta < 247.5))) {
            return 1;
        }
        if (((theta >= 67.5) && (theta < 112.5)) || ((theta >= 247.5) && (theta < 292.5))) {
            return 2;
        }
        if (((theta >= 112.5) && (theta < 157.5)) || ((theta >= 292.5) && (theta < 337.5))) {
            return 3;
        }
        return 0;
    }

    // Function to apply non maxima suppression to the image array
    private int suppress(int[][] m_2d, int sector, int i, int j, int lowthresh) {

        int tmp = m_2d[i][j];
        if (tmp < lowthresh) {
            return 0;
        }

//if (318 < i && i < 322 && 113 < j && j < 117)System.out.println("ij("+i+","+j+") sector: "+sector+" neigh: "+m_2d[i-1][j-1]+" "+m_2d[i-1][j]+" "+m_2d[i-1][j+1]+" "+m_2d[i][j-1]+" "+m_2d[i][j]+" "+m_2d[i][j+1]+" "+m_2d[i+1][j-1]+" "+m_2d[i+1][j]+" "+m_2d[i+1][j+1]);

        if (sector == 0) {
            if ((m_2d[i + 1][j] >= tmp) || (m_2d[i - 1][j] > tmp)) {
                return 0;
            } else {
                return tmp;
            }
        }
        if (sector == 1) {
            if ((m_2d[i + 1][j + 1] >= tmp) || (m_2d[i - 1][j - 1] > tmp)) {
                return 0;
            } else {
                return tmp;
            }
        }
        if (sector == 2) {
            if ((m_2d[i][j + 1] >= tmp) || (m_2d[i][j - 1] > tmp)) {
                return 0;
            } else {
                return tmp;
            }
        }
        if (sector == 3) {
            if ((m_2d[i + 1][j - 1] >= tmp) || (m_2d[i - 1][j + 1] > tmp)) {
                return 0;
            } else {
                return tmp;
            }
        }
        System.out.println("Canny - Unidentified sector " + sector + " at ij: " + i + " " + j);
        return 0;
    }

    /*The function apply_track is used to track the image for suitable lines. It
     *does this by first finding points above the highthreshold. When it finds
     *such a point it then finds surrounding point which are above the low threshold
     *and tracks along them, continually finding points above the low threshold. This
     *is done until the tracker explores all paths from the original point. It then
     *finds the next starting point and starts tracking again.
     */
    private int[][] apply_track(int[][] input, int width, int height,
            int lowthresh, int highthresh) {

        d_w = width;
        d_h = height;

        int[][] marked = new int[d_w][d_h];
        int[][] tracked = new int[d_w][d_h];

        Stack to_track = new Stack();

        //Initialise the marked array
        for (int i = 0; i < d_w; i++) {
            for (int j = 0; j < d_h; j++) {
                marked[i][j] = 0;
            }
        }

        //Now find all the starting points for the tracking
        for (int i = 0; i < d_w; i++) {
            for (int j = 0; j < d_h; j++) {
                //If the point is unmarked and above high threshold then track
                if ((input[i][j] > highthresh) && (marked[i][j] == 0)) {
                    marked = track(input, marked, to_track, lowthresh, i, j);
                }
            }
        }

        //Now clear all the pixels in the input which are unmarked
        for (int i = 0; i < d_w; i++) {
            for (int j = 0; j < d_h; j++) {
                if (marked[i][j] == 0) {
                    tracked[i][j] = 0;
                } else {
                    tracked[i][j] = input[i][j];
                }
            }
        }
        return tracked;
    }

    /*The function track is called once a starting point for tracking has been
     *found. When this happens, this function follows all possible paths above
     *the threshold by placing unsearched paths on the stack. Each time a path
     *is looked at it's pixels are marked. This continues until the stack is
     *empty, at which point the new array of marked paths is returned.
     */
    private int[][] track(int[][] input, int[][] marked, Stack to_track,
            int thresh, int i, int j) {

        //empty represents when the stack is empty
        boolean empty = false;
        int a;
        int b;
        //Create a point to represent where to start the tracking from
        Point current = new Point(i, j);

        //Push the initial point onto the stack
        to_track.push(current);
        while (!empty) {
            try {

                //Take the top pixel from the stack
                current = (Point) to_track.pop();
                //Find it's co-ordinates
                a = current.x;
                b = current.y;
                //Now check neighbourhood and add to stack anything above thresh
                //Only done if pixel is currently unmarked
                if (marked[a][b] == 0) {

                    //Try and track from each neighbouring point
                    if (a > 0 && b > 0) {
                        if (input[a - 1][b - 1] > thresh) {
                            current = new Point((a - 1), (b - 1));
                            to_track.push(current);
                        }
                    }

                    if (b > 0) {
                        if (input[a][b - 1] > thresh) {
                            current = new Point((a), (b - 1));
                            to_track.push(current);
                        }
                    }

                    if (a < (d_w - 1) && b > 0) {
                        if (input[a + 1][b - 1] > thresh) {
                            current = new Point((a + 1), (b - 1));
                            to_track.push(current);
                        }
                    }

                    if (a > 0) {
                        if (input[a - 1][b] > thresh) {
                            current = new Point((a - 1), (b));
                            to_track.push(current);
                        }
                    }

                    if (a < (d_w - 1)) {
                        if (input[a + 1][b] > thresh) {
                            current = new Point((a + 1), (b));
                            to_track.push(current);
                        }
                    }

                    if (a > 0 && b < (d_h - 1)) {
                        if (input[a - 1][b + 1] > thresh) {
                            current = new Point((a - 1), (b + 1));
                            to_track.push(current);
                        }
                    }

                    if (b < (d_h - 1)) {
                        if (input[a][b + 1] > thresh) {
                            current = new Point((a), (b + 1));
                            to_track.push(current);
                        }
                    }

                    if (a < (d_w - 1) && b < (d_h - 1)) {
                        if (input[a + 1][b + 1] > thresh) {
                            current = new Point((a + 1), (b + 1));
                            to_track.push(current);
                        }
                    }

                    //Mark this pixel as having been tracked from
                    marked[a][b] = 1;
                }
            } //If stack empty, then set the empty flag to true
            catch (EmptyStackException e) {
                empty = true;
            }

        }

        return marked;

    }
}
